<?php

/**
 * @author guangting
 *
 */

abstract class DBModel extends MCDbConnection
{
	protected $_table = '';
	protected $_id = '';
	protected $_result = null;
	
	public function __construct()
	{
		parent::__construct();
		$this->attachBehaviors($this->behaviors());
	}
	
	public function behaviors()
	{
		return array();
	}
	
	public function getTable()
	{
		return $this->_table;
	}
	
	public function getId()
	{
		return $this->_id;
	}
	
	public function setTable($table)
	{
		$this->_table = $table;
		return $this;
	}
	
	public function setId($id)
	{
		$this->_id = $id;
		return $this;
	}
	
	public function getResult()
	{
		return $this->_result;
	}
	
	public static function model($className = '')
	{
		static $_instance = array();
		$className .= 'Model';
		if (isset($_instance[$className])) {
			return $_instance[$className];
		}
		
		//PHP 5 >= 5.3.0
		if (function_exists('get_called_class') && empty($className)) {
			$className = get_called_class();
		}
		$_instance[$className] = new $className();
		return $_instance[$className];
	}
	
	private function _getCondition($condition = '', $params = array(), $behaviors = false)
	{
		$command = $this->createCommand()->from($this->_table);
		if (is_array($condition)) {
			if (empty($params) && isset($condition['params'])) {
				$params = $condition['params'];
			}
			foreach ($condition as $method => $value) {
				if ($method == 'condition') {
					$command->where($value, $params);
					continue;
				}
				$method_name = 'set' . ucfirst($method);
				if (method_exists($command, $method_name)) {
					if (!is_array($value)) {
						$value = array($value);
					}
					call_user_func_array(array($command, $method_name), $value);
				}
			}
		} elseif (is_string($condition) && !empty($condition)) {
			$command->where($condition, $params);
		}
		$this->runBehaviors($command, $behaviors);
		return $command;
	}
	
	/**
	 * 
	 * @param string $sql
	 * @param array $params
	 * execute
	 * query
	 * queryAll
	 * queryRow
	 * queryColumn
	 * queryScalar
	 * @param string $query
	 * @return mixed
	 */
	public function queryBySql($sql, $params = array(), $query = 'execute')
	{
		$this->beforeQuery();
		$command = $this->createCommand($sql);
		if ($query == 'execute') {
			$this->_result = call_user_func(array($command, $query), $params);
		} else {
			$command->params = $params;
			$this->_result = call_user_func(array($command, $query));
		}
		$this->afterQuery();
		return $this->_result;
	}
	
	public function findScalar($condition = '', $params = array(), $behaviors = false)
	{
		$this->beforeFind();
		$command = $this->_getCondition($condition, $params, $behaviors);
		$command->limit(1);
		$this->_result = $command->queryScalar();
		$this->afterFind();
		return $this->_result;
	}
	
	public function find($condition = '', $params = array(), $behaviors = false)
	{
		$this->beforeFind();
		$command = $this->_getCondition($condition, $params, $behaviors);
		$command->limit(1);
		$this->_result = $command->queryRow();
		$this->afterFind();
		return $this->_result;
	}
	
	public function findByPk($pk, $behaviors = false)
	{
		$this->beforeFind();
		$condition = $this->_id . '=:' . $this->_id;
		$params = array(':'.$this->_id => $pk);
		$command = $this->_getCondition(array('condition' => $condition), $params, $behaviors);
		$command->limit(1);
		$this->_result = $command->queryRow();
		$this->afterFind();
		return $this->_result;
	}
	
	public function findAll($condition = '', $params = array(), $behaviors = false)
	{
		$this->beforeFind();
		$command = $this->_getCondition($condition, $params, $behaviors);
		$this->_result = $command->queryAll();
		$this->afterFind();
		return $this->_result;
	}
	
	public function findAllByPk($pk, $behaviors = false)
	{
		$this->beforeFind();
		$condition = $this->_id . '=:' . $this->_id;
		$params = array(':'.$this->_id => $pk);
		$command = $this->_getCondition(array('condition' => $condition), $params, $behaviors);
		$this->_result = $command->queryAll();
		$this->afterFind();
		return $this->_result;
	}
	
	public function insert($data, $replace = false)
	{
		$this->beforeSave('insert');
		$sql = $replace === true ? 'REPLACE' : 'INSERT';
		$sql .= ' INTO '.$this->_table.'(%s) VALUES %s';
		$keys = array();
		$values = array();
		$params = array();
		foreach ($data as $field => $item) {
			$idx = ':' . $field;
			$keys[] = $field;
			$values[] = $idx;
			$params[$idx] = $item;
		}
		$keys = implode(',', $keys);
		$values = implode(',', $values);
		$values = '(' . $values . ')';
		$sql = sprintf($sql, $keys, $values);
		$command = $this->createCommand($sql);
		$ret = $command->execute($params);
		$this->_result = $data;
		$this->afterSave('insert');
		return $ret;
	}
	
	public function insertMultiple($data, $replace = false)
	{
		$this->beforeSave('insert');
		$sql = $replace === true ? 'REPLACE' : 'INSERT';
		$sql .= ' INTO '.$this->_table.'(%s) VALUES %s';
		$keys = array();
		$values = array();
		$params = array();
		foreach ($data as $field => $item) {
			$tmp = array();
			foreach ($item as $key => $value) {
				if (empty($keys)) {
					$tmp[] = $key;
				}
				$idx = ':' . $key . $field;
				$values[$field][] = $idx;
				$params[$idx] = $value;
			}
			if (empty($keys)) {
				$keys = $tmp;
			}
			$values[$field] = '(' . implode(',', $values[$field]) . ')';
		}
		$keys = implode(',', $keys);
		$values = implode(',', $values);
		$sql = sprintf($sql, $keys, $values);
		$command = $this->createCommand($sql);
		$ret = $command->execute($params);
		$this->_result = $data;
		$this->afterSave('insert');
		return $ret;
	}
	
	public function update($data, $condition, $params = array())
	{
		$this->beforeSave('update');
		$command = $this->createCommand();
		$ret = $command->update($this->_table, $data, $condition, $params);
		$this->_result = $data;
		$this->afterSave('update');
		return $ret;
	}
	
	public function updateByPk($pk, $data)
	{
		$this->beforeSave('update');
		$command = $this->createCommand();
		$condition = $this->_id . '=:' . $this->_id;
		$params = array(':'.$this->_id => $pk);
		$ret = $command->update($this->_table, $data, $condition, $params);
		$this->_result = $data;
		$this->afterSave('update');
		return $ret;
	}
	
	public function delete($condition, $params = array())
	{
		$this->beforeDelete();
		$command = $this->createCommand();
		$ret = $command->delete($this->_table, $condition, $params);
		$this->afterDelete();
		return $ret;
	}
	
	public function deleteByPk($pk)
	{
		$this->beforeDelete();
		$command = $this->createCommand();
		$condition = $this->_id . '=:' . $this->_id;
		$params = array(':'.$this->_id => $pk);
		$ret = $command->delete($this->_table, $condition, $params);
		$this->afterDelete();
		return $ret;
	}
	
	public function pager($where = '', $pageSize = 20, $behaviors = false, $pageVar = 'p')
	{
		$this->beforeFind();
		$pages = '';
		$models = '';
		$command = $this->createCommand();
		if (!empty($where['from'])) {
			$command->setFrom($where['from']);
		} else {
			$command->from($this->_table);
		}
		if (is_array($where)) {
			$where = array_merge(array('condition' => '', 'params' => array()), $where);
		} else {
			$where = array('condition' => $where, 'params' => array());
		}
		if (!empty($where['condition'])) {
			$command->where($where['condition'], $where['params']);
		}
		$command->select('COUNT(*)');
		$command->limit(1);
		$this->runBehaviors($command, $behaviors);
		$count = $command->queryScalar();
		
		if ($count > 0) {
			$pages = new CPagination($count);
			$pages->pageVar = $pageVar;
			$pages->pageSize = $pageSize;
			$command->reset();
			if (!empty($where['from'])) {
				$command->setFrom($where['from']);
			} else {
				$command->from($this->_table);
			}
			!empty($where['condition']) && $command->where($where['condition'], $where['params']);
			!empty($where['join']) && $command->setJoin($where['join']);
			!empty($where['select']) && $command->setSelect($where['select']);
			!empty($where['order']) && $command->setOrder($where['order']);
			!empty($where['group']) && $command->setGroup($where['group']);
			$command->limit($pages->getLimit());
			$command->offset($pages->getOffset());
			$this->runBehaviors($command, $behaviors);
			$this->_result = $command->queryAll();
			$this->afterFind();
		}
		return array('pages' => $pages, 'res' => $this->_result);
	}
	
	private function runBehaviors(&$command, $behaviors)
	{
		if (is_array($behaviors)) {
			foreach ($behaviors as $method => $value) {
				if (!is_array($value)) {
					$value = array($value);
				}
				call_user_func_array(array($command, $method), $value);
			}
		}
	}
	
	private function beforeQuery()
	{
		if($this->hasEvent('onBeforeQuery')) {
			$this->onBeforeQuery(new CEvent($this));
		}
	}
	
	private function onBeforeQuery($event)
	{
		$this->raiseEvent('onBeforeQuery', $event);
	}
	
	private function afterQuery()
	{
		if($this->hasEvent('onAfterQuery')) {
			$this->onAfterQuery(new CEvent($this));
		}
	}
	
	private function onAfterQuery($event)
	{
		$this->raiseEvent('onAfterQuery', $event);
	}
	
	private function beforeFind()
	{
		if($this->hasEvent('onBeforeFind')) {
			$this->onBeforeFind(new CEvent($this));
		}
	}
	
	private function onBeforeFind($event)
	{
		$this->raiseEvent('onBeforeFind', $event);
	}
	
	private function afterFind()
	{
		if($this->hasEvent('onAfterFind')) {
			$this->onAfterFind(new CEvent($this));
		}
	}
	
	private function onAfterFind($event)
	{
		$this->raiseEvent('onAfterFind', $event);
	}
	
	private function beforeSave($type)
	{
		if($this->hasEvent('onBeforeSave')) {
			$this->onBeforeSave(new CEvent($this));
		}
	}
	
	private function onBeforeSave($event)
	{
		$this->raiseEvent('onBeforeSave', $event);
	}
	
	private function afterSave($type)
	{
		if($this->hasEvent('onAfterSave')) {
			$this->onAfterSave(new CEvent($this));
		}
	}
	
	private function onAfterSave($event)
	{
		$this->raiseEvent('onAfterSave', $event);
	}
	
	private function beforeDelete()
	{
		if($this->hasEvent('onBeforeDelete')) {
			$this->onBeforeDelete(new CEvent($this));
		}
	}
	
	private function onBeforeDelete($event)
	{
		$this->raiseEvent('onBeforeDelete', $event);
	}
	
	private function afterDelete()
	{
		if($this->hasEvent('onAfterDelete')) {
			$this->onAfterDelete(new CEvent($this));
		}
	}
	
	private function onAfterDelete($event)
	{
		$this->raiseEvent('onAfterDelete', $event);
	}
}